/*
Rilego - An easy Images 2 E-Book converter.
Copyright (C) 2011  Federico Improta (Kirys)

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 */
package it.kirys.rilego.engine.processors;

import com.jhlabs.image.DiffusionFilter;
import com.mortennobel.imagescaling.ResampleOp;
import it.kirys.rilego.engine.Engine;
import it.kirys.rilego.engine.loaders.imagesources.IImageSource;
import it.kirys.rilego.engine.loaders.imagesources.SourceTransformation;
import it.kirys.rilego.engine.outputbuilders.ColorPalette;
import it.kirys.rilego.engine.outputbuilders.IMediaType;
import it.kirys.rilego.engine.outputbuilders.ImageSize;
import it.kirys.rilego.engine.processors.imagefilters.FiltersList;
import it.kirys.rilego.engine.processors.imagefilters.IImageFilter;
import it.kirys.rilego.engine.processors.transformers.ITransformer;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ColorConvertOp;
import java.util.LinkedList;

/**
 * This class implements a full image processing process that is based on the orginal
 * JEComics code.
 * @author Federico Improta
 */
public class BasicFullProcessor extends AbstractFullProcessor {

	static BufferedImage toGrayscale(BufferedImage source) {
		BufferedImage image = source;
		BufferedImage gray = new BufferedImage(source.getWidth(), source.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
		Graphics2D out2d = gray.createGraphics();
		out2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
		out2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
		out2d.drawImage(image, 0, 0, null);
		out2d.dispose();
		return gray;
	}

	/**
	 * adapt the an image to a color palette
	 * @param source image to adapt
	 * @param palette palette to adapt the image for
	 * @return the image result of the adaptation.
	 */
	static BufferedImage adaptToPalette(BufferedImage source,ColorPalette palette){
		// First we apply some dithering (the java dither is really bad!
		BufferedImage dither = null;
		DiffusionFilter f = new DiffusionFilter();
		int colors = palette.getColorCount()*3;
		if (colors > 255) {
			colors = 255;
		}
		f.setLevels(colors);
		f.setColorDither(palette.isColor());
		f.setSerpentine(true);
		dither = f.createCompatibleDestImage(source, null);
		f.filter(source, dither);
		
		//then we have to use the correct color model
		BufferedImage out = new BufferedImage(source.getWidth(), source.getHeight(), BufferedImage.TYPE_BYTE_INDEXED,palette.getColorModel());
		Graphics2D out2d = out.createGraphics();
		out2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
		out2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
		out2d.drawImage(dither, 0, 0, null);
		out2d.dispose();

		return out;
	}

	static BufferedImage convertToGrayscale(BufferedImage source) {
		BufferedImageOp op = new ColorConvertOp(
				  ColorSpace.getInstance(ColorSpace.CS_GRAY), null);
		return toGrayscale(op.filter(source, null));
	}

	@Override
	protected BufferedImage[] process(BufferedImage imagesource) {
		LinkedList<BufferedImage> outList = new LinkedList<BufferedImage>();
		// First we prepare the work objects
		BufferedImage images[] = null;
		ColorPalette palette = null; //destination palette if any

		if (mediaType.hasPreferredResolution() && mediaType.getPreferredResolution().hasDestinationPalette()) {
			palette = mediaType.getPreferredResolution().getDestinationPalette();
		}

		/*
		 * First we have to check if we have to work in color or BW
		 */

		if (palette != null && !palette.isColor()) {
			//We make the image grayscale to reduce the effort of filtering
			imagesource = convertToGrayscale(imagesource);
		}

		/*
		 * now we have to check if the image have to be splitted or not, all other 
		 * operation will be done on the array
		 */
		if ((getTransformer() != null) && (source.getTransformations() != null) && (source.getTransformations().isSplit())) {
			images = transformer.split(imagesource);
		} else {
			images = new BufferedImage[]{imagesource};
		}

		// now we do the processing for each image of the array this should be mostly "future proof"
		for (BufferedImage image : images) {
			//Transformation phase
			if ((getTransformer() != null) && (source.getTransformations() != null)) {
				/*
				 * TODO: only the split should be done directly all other transformations
				 * should be managed all inside the transformer ith the transform method.
				 */

				SourceTransformation t = source.getTransformations();
				image = getTransformer().rotate(image, t.getRotate());
			}

			//Resize to the target size before filtering (to reduce load)
			if (mediaType.hasPreferredResolution()) {
				ImageSize size = mediaType.getPreferredResolution();
				int width = size.getWidth();
				int height = size.getHeight();
				int newWidth;
				int newHeight;
				//we have to adapt the image size
				float r = 1.0f * image.getWidth() / image.getHeight();
				float s = 1.0f * width / height;
				if (r > s) {
					newWidth = width;
					newHeight = java.lang.Math.round(width / r);
				} else {
					newWidth = java.lang.Math.round(height * r);
					newHeight = height;
				}
				//I do have to resize
				ResampleOp resample = new ResampleOp(newWidth, newHeight);
				image = resample.filter(image, null);
			}

			/*
			 * Then we have the filter phase
			 */

			if (filters != null) {
				for (IImageFilter filter : filters) {
					image = filter.filter(image);
				}
			}

			/*
			 * Last processing pass is to adapt the color palette if necessary
			 */
			if (palette != null){
				image=adaptToPalette(image, palette);
			}
			
			/*
			 * At last the processed image is added to the output list
			 */

			outList.add(image);
		}

		return outList.toArray(new BufferedImage[]{});
	}

	public BasicFullProcessor(FiltersList filters, ITransformer transformer, IMediaType mediaType, ICompressor compressor, IImageSource source, Engine engine) {
		super(filters, transformer, mediaType, compressor, source, engine);
	}
}
